﻿using AutoMapper;
using WAU.Models.Framework;
using Ganss.XSS;
using Microsoft.AspNetCore.Http;
using NETCore.Encrypt;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using NLog;
using WAU.Models;

namespace WAU.Utils
{
    /// <summary>
    /// 常用实用工具类
    /// </summary>
    public class CommonUtils
    {
        /// <summary>
        /// 静态文件目录
        /// </summary>
        public const string STATIC_FILE_DIR = "wwwroot";

        /// <summary>
        /// 上传文件的同一目录
        /// </summary>
        public const string DEFAULT_UPLOAD_DIR = "Upload";

        /// <summary>
        /// HTML 清洗器（用于去除其中的 <script> 等标签）
        /// </summary>
        public static HtmlSanitizer HtmlSanitizer = new HtmlSanitizer();

        /// <summary>
        /// 当前程序运行的工作路径
        /// </summary>
        private static readonly string CURRENT_PATH = AppDomain.CurrentDomain.BaseDirectory;

        /// <summary>
        /// 锁
        /// </summary>
        private static object LockObj = new object();

        /// <summary>
        /// 日志记录器
        /// </summary>
        private static Logger Logger = NLog.LogManager.GetCurrentClassLogger();

        /// <summary>
        /// 获取认证过用户的角色信息
        /// </summary>
        /// <param name="identityTmp"></param>
        /// <returns></returns>
        public static string[] GetAuthenticatedUserRoles(IIdentity identityTmp)
        {
            var identity = identityTmp as ClaimsIdentity;
            return identity.Claims
                .Where(a => a.Type == ClaimTypes.Role)
                .Select(a => a.Value).ToArray();
        }

        /// <summary>
        /// 计算给定字符串的 MD5（返回小写字符串）
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string CalcMD5(string str)
        {
            var md5 = MD5.Create();
            var bytValue = Encoding.UTF8.GetBytes(str);
            var bytHash = md5.ComputeHash(bytValue);
            var sb = new StringBuilder();
            foreach (var b in bytHash)
            {
                sb.Append(b.ToString("X2"));
            }
            return sb.ToString().ToLower();
        }

        /// <summary>
        /// 创建短的随机字符串
        /// </summary>
        /// <param name="length">长度</param>
        /// <param name="lower">是否为小写</param>
        /// <returns></returns>
        public static string GenerateShortUniqueId(int length = 8, bool lower = true)
        {
            byte[] randomBytes = RandomNumberGenerator.GetBytes(length / 2);
            string resultStr = Convert.ToHexString(randomBytes);
            if(lower) 
            {
                return resultStr.ToLower();
            }
            else
            {
                return resultStr.ToUpper();
            }
        }

        /// <summary>
        /// 重新格式化 IP 地址，用 # 号替代 :
        /// </summary>
        /// <param name="originIpAddress"></param>
        /// <returns></returns>
        public static string FormatIpAddressForCache(string originIpAddress)
        {
            return originIpAddress.Replace(":", "#");
        }

        #region AutoMapper

        /// <summary>
        /// 映射配置缓存
        /// </summary>
        private static readonly IDictionary<string, MapperConfiguration> mapperCfgCache = new ConcurrentDictionary<string, MapperConfiguration>();

        /// <summary>
        /// 使用 AutoMapper 映射对象（基本）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="ignoreFields"></param>
        /// <returns></returns>
        public static TResult Map<TSource, TResult>(TSource source)
        {
            string uniqueTag = $"{ typeof(TSource).FullName }_{ typeof(TResult).FullName }_{ nameof(Map)}";

            bool isExist = mapperCfgCache.TryGetValue(uniqueTag, out MapperConfiguration config);   // 尝试从缓存中取得
            if (!isExist)
            {
                config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMap<TSource, TResult>();
                });
            }

            return config.CreateMapper().Map<TResult>(source);
        }

        /// <summary>
        /// 使用 AutoMapper 映射对象（基本）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="ignoreFields"></param>
        /// <returns></returns>
        public static TResult Map<TSource, TResult>(TSource source, TResult destination)
        {
            string uniqueTag = $"{ typeof(TSource).FullName }_{ typeof(TResult).FullName }_{ nameof(Map) }";

            bool isExist = mapperCfgCache.TryGetValue(uniqueTag, out MapperConfiguration config);   // 尝试从缓存中取得
            if (!isExist)
            {
                config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMap<TSource, TResult>();
                });
            }

            return config.CreateMapper().Map(source, destination);
        }

        /// <summary>
        /// 使用 AutoMapper 映射对象（用于在 Service 层编辑实体时，传入实体 -> 既有实体）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="ignoreFields"></param>
        /// <returns></returns>
        public static TResult MapBaseModel<TSource, TResult>(TSource source, TResult destination)
            where TResult : BaseModel
        {
            string uniqueTag = $"{typeof(TSource).FullName}_{typeof(TResult).FullName}_{ nameof(MapBaseModel) }";

            bool isExist = mapperCfgCache.TryGetValue(uniqueTag, out MapperConfiguration config);   // 尝试从缓存中取得
            if (!isExist)
            {
                config = new MapperConfiguration(cfg =>
                {
                    IMappingExpression<TSource, TResult> exp = cfg.CreateMap<TSource, TResult>();

                    // 添加/更新实体时排除 BaseModel 中的项
                    exp.ForMember(a => a.Id, a => a.Ignore());
                    exp.ForMember(a => a.CreateTime, a => a.Ignore());
                    exp.ForMember(a => a.Creator, a => a.Ignore());
                    exp.ForMember(a => a.Modifier, a => a.Ignore());
                    //exp.ForMember(a => a.ModifyTime, a => a.Ignore());    // 排除后无法拦截并发操作
                });
            }

            return config.CreateMapper().Map(source, destination);
        }

        /// <summary>
        /// 排除 BaseModel 中的默认内部维护字段
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="exp"></param>
        public static void MapperIgnoreBaseModelInner<TSource, TResult>(IMappingExpression<TSource, TResult> exp)
            where TResult: BaseModel
        {
            // 添加更新实体时 BaseModel 中的排除项
            exp.ForMember(a => a.Id, a => a.Ignore());
            exp.ForMember(a => a.CreateTime, a => a.Ignore());
            exp.ForMember(a => a.Creator, a => a.Ignore());
            //exp.ForMember(a => a.ModifyTime, a => a.Ignore());    // 排除后无法拦截并发操作           
        }

        /// <summary>
        /// 使用 AutoMapper 映射对象（用于在 Controller 层映射 基础模型，Dto -> 传入实体）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="ignoreFields"></param>
        /// <returns></returns>
        public static TResult MapBaseModelDto<TSource, TResult>(TSource source, TResult destination,
            Action<IMappingExpression<TSource, TResult>> action = null)
            where TResult : BaseModel
        {
            string uniqueTag = $"{typeof(TSource).FullName}_{typeof(TResult).FullName}_{ nameof(MapBaseModelDto) }";

            bool isExist = mapperCfgCache.TryGetValue(uniqueTag, out MapperConfiguration config);   // 尝试从缓存中取得
            if (!isExist)
            {
                config = new MapperConfiguration(cfg =>
                {
                    IMappingExpression<TSource, TResult> exp = cfg.CreateMap<TSource, TResult>();

                    // 添加/更新实体时排除 BaseModel 中的项
                    // exp.ForMember(a => a.Id, a => a.Ignore());
                    exp.ForMember(a => a.CreateTime, a => a.Ignore());
                    exp.ForMember(a => a.Creator, a => a.Ignore());                    
                    exp.ForMember(a => a.Modifier, a => a.Ignore());
                    // exp.ForMember(a => a.ModifyTime, a => a.Ignore());    // 排除后无法拦截并发操作
                    action?.Invoke(exp);
                });

                if (action == null)
                {
                    mapperCfgCache.TryAdd(uniqueTag, config);
                }
            }

            return config.CreateMapper().Map(source, destination);
        }

        #endregion

        #region 加密

        /// <summary>
        /// AES 加密
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string AESEncrypt(string data)
        {
            return EncryptProvider.AESEncrypt(data, GlobalConfig.Setting.CommonUtils_AESSecretKey);
        }

        /// <summary>
        /// AES 解密
        /// </summary>
        /// <param name="encryptedStr"></param>
        /// <returns></returns>
        public static string AESDecrypt(string encryptedStr)
        {
            return EncryptProvider.AESDecrypt(encryptedStr, GlobalConfig.Setting.CommonUtils_AESSecretKey);
        }

        #endregion

        #region 组合 Lambda 表达式 

        /// <summary>
        /// 将 表达式 以 AndAlso 组合
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="combineType"></param>
        /// <param name="lambdas"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CombineLambdaAndAlso<T>(params Expression<Func<T, bool>>[] lambdas)
        {
            UnifyExpressionVisitor ueVisitor = new UnifyExpressionVisitor();
            ParameterExpression rootPe = ueVisitor.AddExpression(typeof(T));

            // 新建一个原始条件 a => true，并逐个组合数组中的条件
            Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(
                Expression.Constant(true), rootPe);
            for (int i = 0; i < lambdas.Length; i++)
            {
                Expression tmp = ueVisitor.Visit(result.Body);
                Expression tmp1 = ueVisitor.Visit(lambdas[i].Body);
                result = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(tmp, tmp1), rootPe);
            }
            return result;
        }

        /// <summary>
        /// 将类型参数统一的 Visitor 类
        /// </summary>
        private class UnifyExpressionVisitor : ExpressionVisitor
        {
            public List<ParameterExpression> PeList = new List<ParameterExpression>();
            public UnifyExpressionVisitor() { }

            public ParameterExpression AddExpression(Type type)
            {
                // 从 'A' 开始，为每个类型指定一个形参
                var result = Expression.Parameter(type, ((char)(65 + PeList.Count)).ToString());
                PeList.Add(result);
                return result;
            }

            // 覆写 VisitParameter 方法，返回我们指定的统一 Parameter
            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression peTmp = PeList.Find(a => a.Type == p.Type);
                if (peTmp == null)
                {
                    return AddExpression(p.Type);
                }
                else
                {
                    return peTmp;
                }
            }
            public override Expression Visit(Expression node)
            {
                return base.Visit(node);	//Visit会根据VisitParameter()方法返回的Expression修改这里的node变量
            }
        }

        #endregion

        /// <summary>
        /// 将传入的字符串中间部分字符替换成特殊字符
        /// </summary>
        /// <param name="value"></param>
        /// <param name="lengthToReplace"></param>
        /// <param name="specialChar"></param>
        /// <returns></returns>
        public static string ReplaceWithSpecialChar(string value, int lengthToReplace, char specialChar = '*')
        {
            int preLength = (value.Length - lengthToReplace) / 2;

            ReadOnlySpan<char> preStr = value.AsSpan().Slice(0, preLength);
            Enumerable.Repeat(specialChar, lengthToReplace);
            ReadOnlySpan<char> suffixStr = value.AsSpan().Slice(preLength + lengthToReplace);

            return preStr.ToString() + string.Join("", Enumerable.Repeat(specialChar, lengthToReplace)) + suffixStr.ToString();
        }

        #region 上传文件相关

        /// <summary>
        /// 获取用于保存的文件名（如果文件名太长，则截取50个字符，如果后缀名过长，则截取 11 个字符，包含 . 号）
        /// </summary>
        /// <param name="originFormFileName"></param>
        /// <returns></returns>
        public static (string, string) GetFormFileNameForSave(string originFormFileName)
        {
            int lastIndexOfSuffix = originFormFileName.LastIndexOf('.');
            string fileName = "";
            string fileSuffix = "";
            if (lastIndexOfSuffix != -1)
            {
                fileName = originFormFileName.Substring(0, lastIndexOfSuffix);
                fileSuffix = originFormFileName.Substring(lastIndexOfSuffix);
            }
            else
            {
                fileName = originFormFileName;
            }
            
            if (fileName.Length > 50)
            {
                fileName = fileName.Substring(0, 50);
            }

            if(fileSuffix.Length > 11)
            {
                fileSuffix = fileSuffix.Substring(0, 11);
            }

            return (fileName, fileSuffix);
        }

        /// <summary>
        /// 保存上传的文件
        /// </summary>
        /// <param name="files"></param>
        /// <param name="dir"></param>
        /// <returns></returns>
        public static ExecResult<string> SaveUploadFile(IEnumerable<IFormFile> formFiles, string dir = "")
        {
            if (!formFiles.Any())
            {
                return ExecResult<string>.Error("没有要上传的文件");
            }

            IFormFile formFile = formFiles.First();
            (string fileName, string fileSuffix) = GetFormFileNameForSave(formFile.FileName);

            try
            {
                string targetDir = Path.Combine(CURRENT_PATH, STATIC_FILE_DIR, DEFAULT_UPLOAD_DIR, 
                                                    dir, DateTime.Now.ToString("yyyy-MM"));

                DirectoryInfo di = new DirectoryInfo(targetDir);
                if (!di.Exists)
                {
                    di.Create();
                }

                string newFileName = fileName + "-" + GenerateShortUniqueId(10) + fileSuffix;
                string newFileFullName = Path.Combine(targetDir, newFileName);

                using (FileStream fs = new FileStream(newFileFullName, FileMode.CreateNew))
                {
                    formFile.CopyTo(fs);
                }

                return ExecResult<string>.RetSuccess(newFileFullName, "文件上传成功");
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                return ExecResult<string>.Error(ex.Message);
            }
        }

        /// <summary>
        /// 保存上传的文件（多个）
        /// </summary>
        /// <param name="files"></param>
        /// <param name="dir"></param>
        /// <returns></returns>
        public static ExecResult<List<string>> SaveUploadFiles(IEnumerable<IFormFile> formFiles, string dir = "")
        {
            if (!formFiles.Any())
            {
                return ExecResult<List<string>>.Error("没有要上传的文件");
            }

            try
            {
                List<string> resultPathes = new List<string>();
                string targetDir = Path.Combine(CURRENT_PATH, STATIC_FILE_DIR, DEFAULT_UPLOAD_DIR, dir, DateTime.Now.ToString("yyyy-MM"));

                DirectoryInfo di = new DirectoryInfo(targetDir);
                if (!di.Exists)
                {
                    di.Create();
                }

                foreach (var formFile in formFiles)
                {
                    (string fileName, string fileSuffix) = GetFormFileNameForSave(formFile.FileName);

                    string newFileName = fileName + "-" + GenerateShortUniqueId(10) + fileSuffix;
                    string newFileFullName = Path.Combine(targetDir, newFileName);

                    using (FileStream fs = new FileStream(newFileFullName, FileMode.CreateNew))
                    {
                        formFile.CopyTo(fs);
                    }

                    resultPathes.Add(newFileFullName);
                }

                return ExecResult<List<string>>.RetSuccess(resultPathes, "文件全部上传成功");
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                return ExecResult<List<string>>.Error(ex.Message);
            }
        }

        #endregion

        /// <summary>
        /// 将 本地文件系统的相对路径 转换为 url 中的路径（去掉wwwroot目录，\ 号的替换）
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string ConvertLocalFSPathToURLPart(string path)
        {
            if (path.Contains(STATIC_FILE_DIR))
            {
                path = path.Replace(STATIC_FILE_DIR, "");
            }

            return path.Replace(CURRENT_PATH, "").Replace(Path.DirectorySeparatorChar, '/');            
        }

        /// <summary>
        /// 从 Url 中获取文件名
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetFileNameFromUrl(string url)
        {
            int lastIndexOfSlash = url.LastIndexOf('/');
            int firstIndexOfQues = url.IndexOf("?");

            if (lastIndexOfSlash != -1 && firstIndexOfQues != -1) 
            {
                return url.Substring(lastIndexOfSlash + 1, firstIndexOfQues - 1 - lastIndexOfSlash);
            }
            else if(firstIndexOfQues == -1)
            {
                return url.Substring(lastIndexOfSlash + 1);
            }
            else
            {
                return url;
            }
        }

        /// <summary>
        /// 修改文件后缀， <paramref name="newSuffix" /> 需要带 . 号
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="newSuffix"></param>
        /// <returns></returns>
        public static string ChangeFileSuffix(string fileName, string newSuffix)
        {
            int lastIndexOfDot = fileName.LastIndexOf('.');
            if(lastIndexOfDot == -1)
            {
                return fileName + newSuffix;
            }
            else
            {
                ReadOnlySpan<char> fileNameWithoutSufffix = fileName.AsSpan().Slice(0, fileName.LastIndexOf("."));
                return fileNameWithoutSufffix.ToString() + newSuffix;
            }
        }

        /// <summary>
        /// 文件名和后缀 正则
        /// </summary>
        private static Regex FileNameSuffixReg = new Regex("(.+)(\\..*?)$");

        /// <summary>
        /// 向文件名插入内容（后缀名之前）
        /// </summary>
        /// <param name="originFileName"></param>
        /// <param name="strToAppend"></param>
        /// <returns></returns>
        public static string AppendToFileName(string originFileName, string strToAppend)
        {
            if (!FileNameSuffixReg.IsMatch(originFileName))
            {
                return originFileName + strToAppend;
            }
            else
            {
                return FileNameSuffixReg.Replace(originFileName, "${1}" + strToAppend + "${2}");
            }
        }

        /// <summary>
        /// 下载文件（自动命名）
        /// </summary>
        /// <param name="url"></param>
        /// <param name="saveDir"></param>
        /// <param name="fileName"></param>
        public static void DownloadFile(string url, string saveDir, out string fileName)
        {
            fileName = GetFileNameFromUrl(url);

            try
            {
                string path = Path.Combine(CURRENT_PATH, saveDir);
                DirectoryInfo di = new DirectoryInfo(path);
                if (!di.Exists)
                {
                    di.Create();
                }

                string fileFullPath = Path.Combine(path, fileName);

                HttpClient httpClient = new HttpClient();
                using (var stream = httpClient.GetStreamAsync(url).Result)
                using (var fsStream = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write))
                {
                    stream.CopyTo(fsStream);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
        }

        /// <summary>
        /// 下载文件（指定文件名）
        /// </summary>
        /// <param name="url"></param>
        /// <param name="saveDir"></param>
        /// <param name="fileName"></param>
        public static void DownloadFile(string url, string saveDir, string fileName)
        {
            try
            {
                string path = Path.Combine(CURRENT_PATH, saveDir);
                DirectoryInfo di = new DirectoryInfo(path);
                if (!di.Exists)
                {
                    di.Create();
                }

                string fileFullPath = Path.Combine(path, fileName);

                HttpClient httpClient = new HttpClient();
                using (var stream = httpClient.GetStreamAsync(url).Result)
                using (var fsStream = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write))
                {
                    stream.CopyTo(fsStream);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
        }


        /// <summary>
        /// url 编码的正则表达式
        /// </summary>
        private static Regex UrlEncodedReg = new Regex("%.{2}");

        /// <summary>
        /// URL 编码并将转义后的字母大写（原字母不动）。<paramref name="str"/> 是未编码的原始字符串
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string UrlEncodeUpper(string str)
        {
            str = HttpUtility.UrlEncode(str);            
            if(!UrlEncodedReg.IsMatch(str))
            {
                return str;
            }
            else
            {
                return UrlEncodedReg.Replace(str, (match) =>
                {
                    return match.Value.ToUpper();
                });
            }            
        }

        /// <summary>
        /// 创建排序表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sortOrderPair"></param>
        /// <returns></returns>
        public static Expression<Func<T, object>> MakeOrderExpression<T>(string sortField)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "p"); // 创建形参 p
            MemberExpression meProp = Expression.Property(pe, sortField);	// 获取 p 的属性
            return Expression.Lambda<Func<T, object>>(Expression.Convert(meProp, typeof(object)), pe);
        }
    }
}
